/*************************************************************************
 * @file: hedcontroller.cpp
 * @date: 01/04/2012
 * TOPOL v1_0 - (c)copyright 2012 TEC/UFF
 *************************************************************************
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 ************************************************************************/

// includes
//----------
#include "hedcontroller.h"
#include "entity/vertex.h"
#include "entity/edge.h"
#include "entity/face.h"
#include "entity/halfedge.h"  // they should not be here
#include "euleroper/euleroperator.h"
#include "euleroper/mvfs.h"
#include "euleroper/mev.h"
#include "euleroper/mef.h"
#include "euleroper/mvse.h"
#include "euleroper/mvr.h"
#include "euleroper/mekr.h"
#include <iostream>



///////////////////////////////////////////////////
///  Constructor and Destructor Implementation  ///
///////////////////////////////////////////////////

//-------------------------------------------------------------------------
HEDController::HEDController()
{
	//eulerop.setModel(....);
}
//-------------------------------------------------------------------------



///////////////////////////////////////
///  PUBLIC Methods Implementation  ///
///////////////////////////////////////

//-------------------------------------------------------------------------
void 
HEDController::doAddPoint( double _x, double _y, double _z, double _tol )
{
  // always when adding a vertex into the topological data structure,
  // performs the following geometric checks:
  // 1: checks if the model already contains a vertex
  // 2: checks if the coordinate is the same of an existing vertex
  // 3: checks if the coordinate lies on an curve or on an region
  if( m_geomChecker.hasAnyVertex(m_model) ) // substitute it with cheaper test
  {
    int vId = 0;
    if( m_geomChecker.hasVertexWithGivenCoords(m_model,vId,_x,_y,_z,_tol) )
    {
      std::cout << ".WARNING: There is already a vertex with the same coordinate! Id = " << vId << std::endl;
    }
    else
    {
      // finds the edge which the vertex lies, if it really happens!
      if( m_geomChecker.liesOnCurve(m_model,vId,_x,_y,_z,_tol) )
      {
        Edge* edg = m_model.getEdge(_x,_y,_z,_tol);
        int numVtx = m_model.getNumberOfVertexes();
        EulerOperator* mvse = new MVSE(m_model, edg, numVtx+1, _x, _y, _z);
        mvse->execute();
        m_undoredo.insertCommand( mvse );
      }
      else
      {
        // finds the face which the vertex lies, if it really happens!
        Face* fct = m_model.getFace(_x,_y,_z,_tol);
        if( fct != NULL )
        {
          std::cout << ".FACE Id = " << fct->getFaceno() << std::endl;
          int numVtx = m_model.getNumberOfVertexes();
          EulerOperator* mvr = new MVR(m_model,fct, numVtx+1, _x, _y, _z);
          mvr->execute();
          m_undoredo.insertCommand( mvr );
        }
        else
          std::cout << ".ERROR: It did not find a face! " << std::endl;
      }
    }
  }
  else
  {
    EulerOperator* mvfs = new MVFS( m_model,1, 1, 1, _x, _y, _z );
    mvfs->execute();
    m_undoredo.insertCommand( mvfs );
  }
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDController::doAddCurve( double _x1, double _y1, double _z1,
                           double _x2, double _y2, double _z2, double _tol )
{
  // always when adding a curve into the topological data structure,
  // performs the following geometric checks:
  // 1: checks if the model already contains a vertex
  // 1: checks if the curve intersects a vertex of the model
  // 2: checks if the curve intersects another curve of the model
  // 3: checks if one end point of the curve belong to vertex of the model
  // 4: checks if both end points of the curve belong to vertexes of the model
  if( m_geomChecker.hasAnyVertex(m_model) ) // substitute it with cheaper test
  {
    int n = 0;
    list<IPoint> intPts;
    list<Edge*> iEdges;
    IPoint cp1(_x1,_y1,_z1);
    IPoint cp2(_x2,_y2,_z2);
    m_geomChecker.intersectsCurves(m_model,cp1,cp2,intPts,iEdges);
    std::cout << "-------------------- " << std::endl;
    std::cout << ".Testing intersections " << std::endl;
    std::cout << "...INTERSECTIONS = " << intPts.size() << std::endl;
    int v1Id = 0;
    int v2Id = 0;
    bool hasP1 = m_geomChecker.hasVertexWithGivenCoords(m_model,v1Id,_x1,_y1,_z1,_tol);
    bool hasP2 = m_geomChecker.hasVertexWithGivenCoords(m_model,v2Id,_x2,_y2,_z2,_tol);
    // removes from the candidates the points that can be attracted to the curve end points
    list<Edge*>::iterator itEd = iEdges.begin();
    list<IPoint>::iterator itPt = intPts.begin();
    for(; itPt!=intPts.end(); )
    {
      int vId = 0;
      double xi = itPt->getX();
      double yi = itPt->getY();  
      double zi = 0.0;  
      bool hasP = m_geomChecker.hasVertexWithGivenCoords(m_model,vId,xi,yi,zi,_tol);
      if( hasP && (hasP1 && (v1Id == vId)) || (hasP2 && (v2Id == vId)) )
      {
        itPt = intPts.erase(itPt);
        itEd = iEdges.erase(itEd);
      }
      else
      {
        itPt++;
        itEd++;
      }
    }
    std::cout << "...INTERSECTIONS = " << intPts.size() << std::endl;
    int numVtx = m_model.getNumberOfVertexes();
    int numFcs = m_model.getNumberOfFaces();
    if( hasP1 && hasP2 )
    {
      int v2Idi = numVtx+1;
      for(itEd=iEdges.begin(),itPt=intPts.begin(); itPt!=intPts.end(); itPt++,itEd++)
      {
        Edge* edg = *itEd;
        EulerOperator* mvse = new MVSE(m_model,edg, ++numVtx, itPt->getX(), itPt->getY(), 0.0);
        mvse->execute();
        m_undoredo.insertCommand( mvse );
        Vertex* vtx1 = m_model.getVertex(v1Id);
        Vertex* vtx2 = m_model.getVertex(v2Idi);
        HalfEdge* h1 = m_model.getHalfEdge(vtx1,vtx2->getPoint()->getX(),vtx2->getPoint()->getY(),vtx2->getPoint()->getZ());
        HalfEdge* h2 = m_model.getHalfEdge(vtx2,vtx1->getPoint()->getX(),vtx1->getPoint()->getY(),vtx1->getPoint()->getZ());
        // checks if the half-edges have the same loop to decide between MEF and MEKR
        if( h1->getLoop() != h2->getLoop() )
        {
          EulerOperator* mekr = new MEKR( m_model, h1, h2 );
          mekr->execute();
          m_undoredo.insertCommand( mekr );
        }
        else
        {
          EulerOperator* mef = new MEF( m_model, 1, v1Id, v2Idi, ++numFcs );
          mef->execute();
          m_undoredo.insertCommand( mef );
        }
        v1Id = numVtx;
        v2Idi = numVtx+1;
      }
      Vertex* vtx1 = m_model.getVertex(v1Id);
      Vertex* vtx2 = m_model.getVertex(v2Id);
      HalfEdge* h1 = m_model.getHalfEdge(vtx1,vtx2->getPoint()->getX(),vtx2->getPoint()->getY(),vtx2->getPoint()->getZ());
      HalfEdge* h2 = m_model.getHalfEdge(vtx2,vtx1->getPoint()->getX(),vtx1->getPoint()->getY(),vtx1->getPoint()->getZ());
      // checks if the half-edges have the same loop to decide between MEF and MEKR
      if( h1->getLoop() != h2->getLoop() )
      {
        EulerOperator* mekr = new MEKR( m_model, h1, h2 );
        mekr->execute();
        m_undoredo.insertCommand( mekr );
      }
      else
      {
        EulerOperator* mef = new MEF( m_model, 1, v1Id, v2Id, numFcs+1 );
        mef->execute();
        m_undoredo.insertCommand( mef );
      }
    }
    else if( hasP1 || hasP2 )
    {
      // supposing that there are intersections
      if( hasP2 )
      {
        Face* fct1 = m_model.getFace(_x1,_y1,_z1,_tol);
        EulerOperator* mvr = new MVR( m_model,fct1, ++numVtx, _x1, _y1, _z1 );
        mvr->execute();
        m_undoredo.insertCommand( mvr );
        v1Id = numVtx;
      }
      double v2Idi = numVtx+1;
      for( itEd=iEdges.begin(),itPt=intPts.begin(); itPt!=intPts.end(); itPt++,itEd++ )
      {
        Edge* edg = *itEd;
        EulerOperator* mvse = new MVSE(m_model,edg, ++numVtx, itPt->getX(), itPt->getY(), 0.0);
        mvse->execute();
        m_undoredo.insertCommand( mvse );
        Vertex* vtx1 = m_model.getVertex(v1Id);
        Vertex* vtx2 = m_model.getVertex(v2Idi);
        HalfEdge* h1 = m_model.getHalfEdge(vtx1,vtx2->getPoint()->getX(),vtx2->getPoint()->getY(),vtx2->getPoint()->getZ());
        HalfEdge* h2 = m_model.getHalfEdge(vtx2,vtx1->getPoint()->getX(),vtx1->getPoint()->getY(),vtx1->getPoint()->getZ());
        // checks if the half-edges have the same loop to decide between MEF and MEKR
        if( h1->getLoop() != h2->getLoop() )
        {
          EulerOperator* mekr = new MEKR( m_model, h1, h2 );
          mekr->execute();
          m_undoredo.insertCommand( mekr );
        }
        else
        {
          EulerOperator* mef = new MEF( m_model, 1, v1Id, v2Idi, ++numFcs );
          mef->execute();
          m_undoredo.insertCommand( mef );
        }
        v1Id = numVtx;
        v2Idi = numVtx+1;
      }
      if( hasP1 )
      {
        EulerOperator* mev = new MEV( m_model, 1, v1Id, numVtx+1, _x2, _y2, _z2 );
        mev->execute();
        m_undoredo.insertCommand( mev );
      }
      if( hasP2 )
      {
        Vertex* vtx1 = m_model.getVertex(v1Id);
        Vertex* vtx2 = m_model.getVertex(v2Id);
        HalfEdge* h1 = m_model.getHalfEdge(vtx1,vtx2->getPoint()->getX(),vtx2->getPoint()->getY(),vtx2->getPoint()->getZ());
        HalfEdge* h2 = m_model.getHalfEdge(vtx2,vtx1->getPoint()->getX(),vtx1->getPoint()->getY(),vtx1->getPoint()->getZ());
        // checks if the half-edges have the same loop to decide between MEF and MEKR
        if( h1->getLoop() != h2->getLoop() )
        {
          EulerOperator* mekr = new MEKR( m_model, h1, h2 );
          mekr->execute();
          m_undoredo.insertCommand( mekr );
        }
        else
        {
          EulerOperator* mef = new MEF( m_model, 1, v1Id, v2Id, ++numFcs );
          mef->execute();
          m_undoredo.insertCommand( mef );
        }
      }
    }
    else
    {
      // checks if the curve starts from a point in one edge
      int vId = 0;
      if( m_geomChecker.liesOnCurve(m_model,vId,_x1,_y1,_z1,_tol)  )
      {
        Edge* edg1 = m_model.getEdge(_x1,_y1,_z1,_tol);
        // removes from the list if it coincides
        if( intPts.size() > 0 ) 
        {
          itEd = iEdges.begin();
          itPt = intPts.begin();
          if( edg1 == *(itEd) )
          {
            intPts.erase(itPt);
            iEdges.erase(itEd);
          }
        }
        EulerOperator* mvse = new MVSE(m_model,edg1, ++numVtx, _x1, _y1, _z1);
        mvse->execute();
        m_undoredo.insertCommand( mvse );
      }
      else
      {
        // finds the face which the point lies, if it really happens!
        Face* fct1 = m_model.getFace(_x1,_y1,_z1,_tol);
        EulerOperator* mvr = new MVR( m_model,fct1, ++numVtx, _x1, _y1, _z1 );
        mvr->execute();
        m_undoredo.insertCommand( mvr );
      }
      // checks if the curve ends in a point in one edge
      bool doTheLastMEV = true;
      if( m_geomChecker.liesOnCurve(m_model,vId,_x2,_y2,_z2,_tol)  )
      {
        Edge* edg2 = m_model.getEdge(_x2,_y2,_z2,_tol);
        // adds to the list if it does not coincide
        if( intPts.size() > 0 ) 
        {
          itEd = iEdges.end();
          itPt = intPts.end();
          if( edg2 == *(--itEd) )
          {
            intPts.erase(--itPt);
            iEdges.erase(itEd);
          }
        }
        intPts.push_back(IPoint(_x2,_y2,_z2));
        iEdges.push_back(edg2);
        doTheLastMEV = false;
      }
      v1Id = numVtx;
      v2Id = numVtx+1;
      // supposing that there are intersections
      for(itEd=iEdges.begin(),itPt=intPts.begin(); itPt!=intPts.end(); itPt++,itEd++)
      {
        Edge* edg = *itEd;
        EulerOperator* mvse = new MVSE(m_model,edg, ++numVtx, itPt->getX(), itPt->getY(), 0.0);
        mvse->execute();
        m_undoredo.insertCommand( mvse );
        Vertex* vtx1 = m_model.getVertex(v1Id);
        Vertex* vtx2 = m_model.getVertex(v2Id);
        HalfEdge* h1 = m_model.getHalfEdge(vtx1,vtx2->getPoint()->getX(),vtx2->getPoint()->getY(),vtx2->getPoint()->getZ());
        HalfEdge* h2 = m_model.getHalfEdge(vtx2,vtx1->getPoint()->getX(),vtx1->getPoint()->getY(),vtx1->getPoint()->getZ());
        // checks if the half-edges have the same loop to decide between MEF and MEKR
        if( h1->getLoop() != h2->getLoop() )
        {
          EulerOperator* mekr = new MEKR( m_model, h1, h2 );
          mekr->execute();
          m_undoredo.insertCommand( mekr );
        }
        else
        {
          EulerOperator* mef = new MEF( m_model, 1, v1Id, v2Id, ++numFcs );
          mef->execute();
          m_undoredo.insertCommand( mef );
        }
        v1Id = numVtx;
        v2Id = numVtx+1;
      }
      if( doTheLastMEV )
      {
        EulerOperator* mev = new MEV( m_model, 1, v1Id, numVtx+1, _x2, _y2, _z2 );
        mev->execute();
        m_undoredo.insertCommand( mev );
      }
    }
  }
  else
  {
    EulerOperator* mvfs = new MVFS( m_model, 1, 1, 1, _x1, _y1, _z1 );
    mvfs->execute();
    EulerOperator* mev = new MEV( m_model, 1, 1, 2, _x2, _y2, _z2 );
    mev->execute();
    m_undoredo.insertCommand( mvfs );
    m_undoredo.insertCommand( mev );
  }
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDController::doAddTriangle( double _x1, double _y1, double _z1,
                              double _x2, double _y2, double _z2,
                              double _x3, double _y3, double _z3 )
{
//   EulerOperator* mvfs = new MVFS( m_model,1, 1, 1, _x1, _y1, _z1 );
//   mvfs->execute();
//   EulerOperator* mev1 = new MEV( m_model, 1, 1, 1, 1, 1, 1, 2, _x2, _y2, _z2 );
//   mev1->execute();
//   EulerOperator* mev2 = new MEV( m_model, 1, 1, 1, 2, 1, 1, 3, _x3, _y3, _z3 );
//   mev2->execute();
//   EulerOperator* mef  = new MEF( m_model, 1, 1, 1, 1, 1, 1, 2 );
//   mef->execute();
//   m_undoredo.insertCommand( mvfs );
//   m_undoredo.insertCommand( mev1 );
//   m_undoredo.insertCommand( mev2 );
//   m_undoredo.insertCommand( mef );
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDController::doSelection( double _x, double _y, double _z, double _tol )
{
  Vertex* v = m_model.getVertex(_x,_y,_z,_tol);
  if(v != NULL)
  {
    v->setSelection(true);
  }
  else
  {
    Edge* e = m_model.getEdge(_x,_y,_z,_tol);
    if(e != NULL)
    {
      e->setSelection(true);
    }
    else
    {
      Face* f = m_model.getFace(_x,_y,_z,_tol);
      if( f != NULL )
        if( f->getLoopOut() != NULL ) // do not select the external face
          f->setSelection(true);
    }
  }
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDController::doUnSelection()
{
  vector<Vertex*> vertices = m_model.getVertexes();
  for( size_t i=0; i< vertices.size(); i++ )
  {
    vertices[i]->setSelection(false);
  }
  vector<Edge*> edges = m_model.getEdges();
  for( size_t i=0; i< edges.size(); i++ )
  {
    edges[i]->setSelection(false);
  }
  vector<Face*> faces = m_model.getFaces();
  for( size_t i=0; i< faces.size(); i++ )
  {
    faces[i]->setSelection(false);
  }
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDController::doUndo()
{ 
  m_undoredo.undo(); 
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
void 
HEDController::doRedo() 
{ 
  m_undoredo.redo(); 
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
HEDModel 
HEDController::getModel()
{
  return m_model;
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
bool 
HEDController::hasUndo()
{
  return m_undoredo.hasUndo();
}
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
bool 
HEDController::hasRedo()
{
  return m_undoredo.hasRedo();
}
//-------------------------------------------------------------------------
